package com.mapforge.map.tools.tilewriting;

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;

import com.mapforge.map.TileLayer;
import com.mapforge.map.TileMap;
import com.mapforge.selection.ContinuousSelection;
import com.mapforge.selection.Selection;
import com.mapforge.selection.TileSelection;
import com.mapforge.selection.UncheckedCompositeSelection;

/**
 * The {@link LineTool} class represents a {@link AbstractTileWriterTool} which
 * can write a tile selection to an underlying destination {@link TileLayer} in
 * the standard {@link AbstractTileWriterTool} pattern.
 * 
 * @author Will Morrison
 * 
 */
public class LineTool extends AbstractTileWriterTool {
	private List<Selection> selections;

	public LineTool() {
		super("Line tool");
		selections = new ArrayList<Selection>();
	}

	private static List<Point> calculateLine(int x0, int y0, int x1, int y1,
			int width) {
		int dy = y1 - y0;
		int dx = x1 - x0;
		int stepx, stepy;
		List<Point> points = new ArrayList<Point>();
		if (dy < 0) {
			dy = -dy;
			stepy = -width;
		} else {
			stepy = width;
		}
		if (dx < 0) {
			dx = -dx;
			stepx = -1;
		} else {
			stepx = 1;
		}
		dy <<= 1;
		dx <<= 1;

		y0 *= width;
		y1 *= width;

		points.add(toPoint(x0 + y0, width));
		if (dx > dy) {
			int fraction = dy - (dx >> 1);
			while (x0 != x1) {
				if (fraction >= 0) {
					y0 += stepy;
					fraction -= dx;
				}
				x0 += stepx;
				fraction += dy;
				points.add(toPoint(x0 + y0, width));
			}
		} else {
			int fraction = dx - (dy >> 1);
			while (y0 != y1) {
				if (fraction >= 0) {
					x0 += stepx;
					fraction -= dy;
				}
				y0 += stepy;
				fraction += dx;
				points.add(toPoint(x0 + y0, width));
			}
		}
		return points;
	}

	private static Point toPoint(int val, int width) {
		int y = val / width;
		return new Point(val - y * width, y);
	}

	@Override
	public void beginEdit() {
		super.beginEdit();
		this.validateState();
		this.getTileWriter().beginPreview();
		this.makeSelection();
	}

	@Override
	public void performEdit() {
		this.validateState();
		this.makeSelection();
	}

	@Override
	public void endEdit() {
		super.endEdit();
		this.validateState();
		this.makeSelection();
		this.getTileWriter().endPreview();
		this.getTileWriter().beginWrite();
		// TODO undo/redo support
		for (int i = 0; i < selections.size(); i++) {
			Selection destination = this.selections.get(i);
			this.getTileWriter().writeTiles(getTileSelection(), destination);
		}
		this.getTileWriter().endWrite();
	}

	@Override
	public void paintTilePreview(Graphics2D g) {
		System.out.println("lineTool: paint tile preview");
		for (Selection s : selections) {
			paintTilePreview(g, getAnchorPoint(), getMovingPoint(), s,
					getTileSelection());
		}
	}

	public void paintTilePreview(Graphics2D g, Point startingPoint,
			Point currentPoint, Selection destination,
			TileSelection tileSelection) {
		TileMap tilemap = getTileMap();
		int x = Math.max(tilemap.getClipX() / tilemap.getTileWidth(), 0);
		int y = Math.max(tilemap.getClipY() / tilemap.getTileHeight(), 0);
		Rectangle rect = new Rectangle(x, y, Math.min(tilemap.getClipWidth()
				/ tilemap.getTileWidth() + 1, tilemap.getTotalHorizontalTiles()
				- x), Math.min(tilemap.getClipHeight()
				/ tilemap.getTileHeight() + 1, tilemap.getTotalVerticalTiles()
				- y));
		destination.intersect(new ContinuousSelection(rect));
		getTileWriter().previewWrite(g, tileSelection, destination);
	}

	/*
	 * private void privateMakeSelection() { selections.clear(); Point
	 * anchorPoint = getAnchorPoint(); Point movingPoint = getMovingPoint();
	 * List<Point> points = calculateLine(anchorPoint.x, anchorPoint.y,
	 * movingPoint.x, movingPoint.y, getTileLayer() .getTotalHorizontalTiles());
	 * int selectedWidth = this.getTileSelection().getBoundingRectangle().width;
	 * int selectedHeight =
	 * this.getTileSelection().getBoundingRectangle().height; Rectangle
	 * currentRect = new Rectangle(); Rectangle prevRect = new Rectangle();
	 * Point point; for (int i = 0; i < points.size(); i++) { point =
	 * points.get(i); currentRect.setFrame(point.x, point.y, selectedWidth,
	 * selectedHeight); while (i + 1 < points.size() &&
	 * prevRect.intersects(currentRect)) { ++i; point = points.get(i);
	 * currentRect.setFrame(point.x, point.y, selectedWidth, selectedHeight); }
	 * if (!prevRect.intersects(currentRect)) { selections.add(new
	 * ContinuousSelection(new Rectangle( currentRect))); } // valid rectangle,
	 * set frame prevRect.setFrame(currentRect); } }
	 */

	@Override
	public Selection performSelection(Point anchorPoint, Point movingPoint) {
		selections.clear();
		List<Point> points = calculateLine(anchorPoint.x, anchorPoint.y,
				movingPoint.x, movingPoint.y, getTileLayer()
						.getTotalHorizontalTiles());
		int selectedWidth = this.getTileSelection().getBoundingRectangle().width;
		int selectedHeight = this.getTileSelection().getBoundingRectangle().height;
		Selection selection = new UncheckedCompositeSelection();
		Point point;
		Rectangle currentRect = new Rectangle();
		Rectangle prevRect = new Rectangle();
		Selection tempSelection;

		for (int i = 0; i < points.size(); i++) {
			point = points.get(i);
			currentRect.setFrame(point.x, point.y, selectedWidth,
					selectedHeight);
			while (i + 1 < points.size() && prevRect.intersects(currentRect)) {
				++i;
				point = points.get(i);
				currentRect.setFrame(point.x, point.y, selectedWidth,
						selectedHeight);
			}
			if (!prevRect.intersects(currentRect)) {
				tempSelection = new ContinuousSelection(new Rectangle(
						currentRect));
				selection.add(tempSelection);
				selections.add(tempSelection);
			}
			// valid rectangle, set frame
			prevRect.setFrame(currentRect);
		}
		return selection;
	}
}
